/*
 * Copyright (c) 2009, Luke Emrose
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the
 * distribution.  Neither the name of Luke Emrose Sole Trader nor the
 * names of its contributors may be used to endorse or promote
 * products derived from this software without specific prior written
 * permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */
 
 // This code is based on code from Ken Perlin's web page at http://mrl.nyu.edu/~perlin/noise
// Improved Noise Copyright 2002 Ken Perlin

#include <iostream>

#include <rx.h>
#include <RslPlugin.h>

#include <OpenEXR/ImathVec.h>

#include "improvedPerlin.hpp"
#include "waveletNoise.hpp"
#include "voronoiNoise.hpp"

// convenience definition
#define rslFunction( x ) RSLEXPORT int x( RslContext* rslContext, int argc, const RslArg* argv[] )

/**
 * @brief
 * initialize all of the different noise tables
 */
void initNoiseTables( RixContext *ctx )
{
#ifdef USE_SSE
	int i = _set_SSE2_enable(1);
	if( i )
	{
		std::cout << "SSE2 enabled." << std::endl;
	}
	else
	{
		std::cout << "SSE2 not enabled; processor does not support SSE2." << std::endl;
	}
#endif

	// initialize improved perlin noise
	initImprovedPerlinNoiseTable( ctx );

	// initialize wavelet noise
	initWaveletNoiseTile< RtFloat >( 128, 128 / 16 );
}

/**
 * @brief
 * clear all of the different noise tables
 */
void clearNoiseTables( RixContext *ctx )
{
	clearWaveletNoiseTile();
}

extern "C"
{
	/**
	 * @brief
	 * fractal brownian motion, multiple octave 3delight noise
	 */
	rslFunction( noiseFBM )
	{
		// get arguments
		RslFloatIter retArg( argv[ 0 ] );
		RslPointIter pointArg( argv[ 1 ] );
		RslFloatIter octavesArg( argv[ 2 ] );
		RslFloatIter lacunarityArg( argv[ 3 ] );
		RslFloatIter gainArg( argv[ 4 ] );

		int numVals = argv[ 0 ]->NumValues();

		for( int i = 0; i < numVals; ++i )
		{
			// initialize all octave variables
			RtFloat amp = 1;
			RtFloat freq = 1;
			RtFloat norm = 0;
			RtFloat noiseSum = 0;

			RtPoint pos;

			// iterate over all octaves, and sum up each octave of noise
			for( int octave = 0; octave < *octavesArg; ++octave )
			{
				// gather position
				pos[ 0 ] = ( *pointArg )[ 0 ] * freq;
				pos[ 1 ] = ( *pointArg )[ 1 ] * freq;
				pos[ 2 ] = ( *pointArg )[ 2 ] * freq;

				RtFloat noiseVal;
				RtInt success = RxNoise( 3, pos, 1, &noiseVal );
				noiseSum += amp * noiseVal;

				// update values for next iteration
				norm += amp;
				freq *= *lacunarityArg;
				amp *= *gainArg;
			}

			// normalize resulting noise value
			*retArg = noiseSum / norm;

			// move to next sample point
			++retArg;
			++pointArg;
			++octavesArg;
			++lacunarityArg;
			++gainArg;
		}

		return 0;
	}

	/**
	 * @brief
	 * improved perlin noise
	 */
	rslFunction( improvedNoise )
	{
		// get arguments
		RslFloatIter retArg( argv[ 0 ] );
		RslPointIter pointArg( argv[ 1 ] );

		int numVals = argv[ 0 ]->NumValues();

		for( int i = 0; i < numVals; ++i )
		{
			*retArg = improvedNoise_impl< RtFloat >( *pointArg );
			
			// move to next sample point
			++retArg;
			++pointArg;
		}

		return 0;
	}

	/**
	 * @brief
	 * fractal brownian motion, multiple octave improved perlin noise
	 */
	rslFunction( improvedNoiseFBM )
	{
		// get arguments
		RslFloatIter retArg( argv[ 0 ] );
		RslPointIter pointArg( argv[ 1 ] );
		RslFloatIter octavesArg( argv[ 2 ] );
		RslFloatIter lacunarityArg( argv[ 3 ] );
		RslFloatIter gainArg( argv[ 4 ] );

		int numVals = argv[ 0 ]->NumValues();

#ifndef USE_SSE
		Imath::Vec3< RtFloat > p;
#endif

		for( int i = 0; i < numVals; ++i )
		{
#ifdef USE_SSE
			// load in the position
			const __m128 p = _mm_set_ps( 0, ( *pointArg )[ 2 ], ( *pointArg )[ 1 ], ( *pointArg )[ 0 ] );
#else
			// gather position
			p.x = ( *pointArg )[ 0 ];
			p.y = ( *pointArg )[ 1 ];
			p.z = ( *pointArg )[ 2 ];
#endif

			// calculate multi-octave fbm
			*retArg = improvedNoiseFBM_impl< RtFloat >
						(
							p,
							*octavesArg,
							*lacunarityArg,
							*gainArg
						);

			// move to next sample point
			++retArg;
			++pointArg;
			++octavesArg;
			++lacunarityArg;
			++gainArg;
		}

		return 0;
	}

	/**
	 * @brief
	 * turbulence, multiple octave improved perlin noise
	 */
	rslFunction( improvedNoiseTurbulence )
	{
		// get arguments
		RslFloatIter retArg( argv[ 0 ] );
		RslPointIter pointArg( argv[ 1 ] );
		RslFloatIter octavesArg( argv[ 2 ] );
		RslFloatIter lacunarityArg( argv[ 3 ] );
		RslFloatIter gainArg( argv[ 4 ] );

		int numVals = argv[ 0 ]->NumValues();

		Imath::Vec3< RtFloat > p;

		for( int i = 0; i < numVals; ++i )
		{
			// gather position
			p.x = ( *pointArg )[ 0 ];
			p.y = ( *pointArg )[ 1 ];
			p.z = ( *pointArg )[ 2 ];

			// calculate multi-octave turbulence
			*retArg = improvedNoiseTurbulence_impl< RtFloat >
						(
							p,
							*octavesArg,
							*lacunarityArg,
							*gainArg
						);


			// move to next sample point
			++retArg;
			++pointArg;
			++octavesArg;
			++lacunarityArg;
			++gainArg;
		}

		return 0;
	}

	/**
	 * @brief
	 * voronoi cell noise ( a.k.a. Worley noise ) -- 3-D, 1-feature version
	 */
	rslFunction( voronoiNoise_f1_3d )
	{
		// get arguments
		RslFloatIter retArg( argv[ 0 ] );
		RslPointIter pointArg( argv[ 1 ] );
		RslFloatIter jitterArg( argv[ 2 ] );

		int numVals = argv[ 0 ]->NumValues();

		for( int i = 0; i < numVals; ++i )
		{
			*retArg = voronoiNoise_f1_3d< RtFloat >
						(
							( *pointArg )[ 0 ],
							( *pointArg )[ 1 ],
							( *pointArg )[ 2 ],
							*jitterArg
						);

			// move to next sample point
			++retArg;
			++pointArg;
			++jitterArg;
		}

		return 0;
	}

	/**
	 * @brief
	 * wavelet noise
	 */
	rslFunction( waveletNoise )
	{
		// get arguments
		RslFloatIter retArg( argv[ 0 ] );
		RslPointIter pointArg( argv[ 1 ] );

		int numVals = argv[ 0 ]->NumValues();

		for( int i = 0; i < numVals; ++i )
		{
			// calculate noise
			*retArg = waveletNoise< RtFloat >( *pointArg );

			// move to next sample point
			++retArg;
			++pointArg;
		}

		return 0;
	}

	/**
	 * @brief
	 * wavelet noise
	 */
	rslFunction( waveletProjectedNoise )
	{
		// get arguments
		RslFloatIter retArg( argv[ 0 ] );
		RslPointIter pointArg( argv[ 1 ] );
		RslNormalIter normArg( argv[ 2 ] );

		int numVals = argv[ 0 ]->NumValues();

		for( int i = 0; i < numVals; ++i )
		{
			// calculate noise
			*retArg = waveletProjectedNoise( *pointArg, *normArg );

			// move to next sample point
			++retArg;
			++pointArg;
			++normArg;
		}

		return 0;
	}

	/**
	 * @brief
	 * wavelet noise
	 */
	rslFunction( waveletMultiBandNoise )
	{
		// get arguments
		RslFloatIter retArg( argv[ 0 ] );
		RslPointIter pointArg( argv[ 1 ] );
		RslNormalIter normArg( argv[ 2 ] );
		RslFloatIter scaleArg( argv[ 3 ] );

		int numVals = argv[ 0 ]->NumValues();

		for( int i = 0; i < numVals; ++i )
		{
			// calculate noise
			int firstband = 0;
			const int nbands = 5;
			RtFloat weights[ nbands ] = { 1.0, 1.0 / 2.0, 1.0 / 4.0, 1.0 / 8.0, 1 / 16.0 };

			*retArg = waveletMultibandNoise< RtFloat >
						(
							*pointArg,
							*scaleArg,
							*normArg,
							firstband,
							nbands,
							&( weights[ 0 ] )
						);

			// move to next sample point
			++retArg;
			++pointArg;
			++normArg;
			++scaleArg;
		}

		return 0;
	}

	/**
	 * @brief
	 * define shading functions that can be called from this rsl plugin
	 */
	static RslFunction myFunctions[] =
	{
		{ "float noiseFBM(point,float,float,float)", noiseFBM, 0, 0 },
		
		{ "float improvedNoise(point)", improvedNoise, 0, 0 },
		{ "float improvedNoise(color)", improvedNoise, 0, 0 },
		{ "float improvedNoise(normal)", improvedNoise, 0, 0 },
		{ "float improvedNoise(vector)", improvedNoise, 0, 0 },

		{ "float improvedNoiseFBM(point,float,float,float)", improvedNoiseFBM, 0, 0 },
		{ "float improvedNoiseTurbulence(point,float,float,float)", improvedNoiseTurbulence, 0, 0 },
		
		{ "float voronoiNoise_f1_3d(point,float)", voronoiNoise_f1_3d, 0, 0 },

		{ "float waveletNoise(point)", waveletNoise, 0, 0 },
		{ "float waveletProjectedNoise(point,normal)", waveletProjectedNoise, 0, 0 },
		{ "float waveletMultiBandNoise(point,normal,float)", waveletMultiBandNoise, 0, 0 },
		0
	};

	/**
	 * @brief
	 * register function table with renderer
	 */
	RSLEXPORT RslFunctionTable RslPublicFunctions( myFunctions, initNoiseTables, clearNoiseTables );

};  // extern "C"
